//      Copyright (c) Microsoft Corporation. All rights reserved.
#include "Common.h"

#include <Sddl.h>

#include <PortableDevice.h>
#include <PortableDeviceTypes.h>

static const WCHAR REG_NAME_APPORDER[] = L"AppOrder-";

static const WCHAR REG_NAME_LAST_ASSIGNED_USER[] = L"LastAssignedUser";

/////////////////////////////////////////////////////////////////////////
//
// CWssBasicDDI()
//
// Constructor.
//
/////////////////////////////////////////////////////////////////////////
CWssBasicDDI::CWssBasicDDI() :
    m_pUserSID(NULL), m_cbUserSID(0)
{
}

/////////////////////////////////////////////////////////////////////////
//
// ~CWssBasicDDI()
//
// Destructor
//
/////////////////////////////////////////////////////////////////////////
CWssBasicDDI::~CWssBasicDDI()
{
    // Delete the user SID
    if (m_pUserSID)
    {
        LocalFree(m_pUserSID);
        m_pUserSID = NULL;
    }
}

/////////////////////////////////////////////////////////////////////////
//
// Initialize()
//
// Perform initialization here.
//
// Parameters: None
//
// Return Values:
//      S_OK:
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::Initialize(IWDFDevice* pWdfDevice, IWDFNamedPropertyStore* pStore)
{
    HRESULT hr = S_OK;

    if (NULL != pStore)
    {
        m_pWdfDevice = pWdfDevice;
        
        m_pPropertyStore = pStore;
        
#ifdef _ASSIGNED_USER_MODEL
        //
        // load last assigned user SID from registry
        //
        PROPVARIANT pv;

        PropVariantInit(&pv);

        HRESULT hrTemp = m_pPropertyStore->GetNamedValue(REG_NAME_LAST_ASSIGNED_USER, &pv);
        
        if (SUCCEEDED(hrTemp))
        {
            if (pv.vt == VT_LPWSTR)
            {
                if (m_pUserSID)
                {
                    LocalFree(m_pUserSID);
                    m_pUserSID = NULL;
                    m_cbUserSID = 0;
                }
                
                if (ConvertStringSidToSid(pv.pwszVal , (PSID *)&m_pUserSID))
                {
                    m_cbUserSID = GetLengthSid(m_pUserSID);
                }
            }
            PropVariantClear(&pv);
        }
#endif
    }
    else
    {
        hr = E_INVALIDARG;
    }

    return hr;
}

/////////////////////////////////////////////////////////////////////////
//
// CWssBasicDDI::Deinitialize
//
// Perform deinitialization here
//
// Return Values:
//      S_OK:
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::Deinitialize(void)
{
    HRESULT hr = S_OK;

    APPLICATION_ID* pApps = NULL;
    DWORD           cApps = 0;

    // Store the current gadget order so it can be used again next time
    if (NULL != g_pDataManager)
    {
        hr = g_pDataManager->GetApplications(&pApps, &cApps);
        if (SUCCEEDED(hr))
        {
            hr = SetStoredApplicationOrder(pApps, cApps);

            CoTaskMemFree(pApps);
        }
    }

    m_pPropertyStore.Release();

    m_pWdfDevice.Release();

    return hr;
}


/////////////////////////////////////////////////////////////////////////
//
// CWssBasicDDI::GetApplicationOrderString
//
// This helper method returns a string that can be used as a key to
// locate the ApplicationOrder setting in the property store.
//
// Parameters:
//      ppwszKey - pointer to a pointer to string
//
// Return Values:
//      S_OK: successfully generated the string
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::GetApplicationOrderString(__deref_out LPWSTR* ppwszKey)
{
    if (NULL == ppwszKey)
    {
        return E_INVALIDARG;
    }

    if (NULL == m_pUserSID ||
        0 == IsValidSid(m_pUserSID))
    {
        return E_UNEXPECTED;
    }

    HRESULT hr      = S_OK;
    LPWSTR  wszSid  = NULL;
    size_t  cchSid  = 0;

    // Initialize out parameter
    *ppwszKey = NULL;

    //
    // Convert the SID to a string, and prepend the AppOrder string
    //
    if (ConvertSidToStringSid(m_pUserSID, &wszSid))
    {
        hr = StringCchLength(wszSid, SECURITY_MAX_SID_SIZE, &cchSid);
        if (SUCCEEDED(hr))
        {
            const size_t cchKeyName = cchSid + sizeof(REG_NAME_APPORDER)/sizeof(WCHAR) + 1;

            *ppwszKey = (LPWSTR)::CoTaskMemAlloc(cchKeyName * sizeof(WCHAR));
            if (NULL == *ppwszKey)
            {
                hr = E_OUTOFMEMORY;
            }
            else
            {
                hr = StringCchCopy(*ppwszKey, cchKeyName, REG_NAME_APPORDER);
                if (SUCCEEDED(hr))
                {
                    hr = StringCchCat(*ppwszKey, cchKeyName, wszSid);
                }
            }
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    if (NULL != wszSid)
    {
        LocalFree(wszSid);
    }

    if (FAILED(hr) && NULL != *ppwszKey)
    {
        ::CoTaskMemFree(*ppwszKey);
        *ppwszKey = NULL;
    }

    return hr;
}

/////////////////////////////////////////////////////////////////////////
//
// OnSetCurrentUser()
//
// Sets the current user of the device.
//
// Parameters:
//      user [in]
//          A const pointer to a SID that identifies the new current user of the
//          device.  Note that ownership of the SID is not passed to the callee.
//
// Return Values:
//      S_OK: The user has been set to the SID passed in, or the SID passed in
//            is the same as the user currently set
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnSetCurrentUser(/*[in]*/ const SID* pUser)
{
    HRESULT hr = S_OK;

    if (NULL == pUser)
    {
        return E_POINTER;
    }

///////
#if DBG
    WCHAR wszBuff[128];
    WCHAR wszName[64];
    WCHAR wszDomain[64];
    DWORD cchName = COUNTOF(wszName);
    DWORD cchDomain = COUNTOF(wszDomain);
    SID_NAME_USE SidType;

    LookupAccountSid(NULL, (SID *)pUser, wszName, &cchName, wszDomain, &cchDomain, &SidType);

    StringCchPrintf(wszBuff, COUNTOF(wszBuff), L"OnSetCurrentUser %s\\%s\r\n", wszDomain, wszName);
    OutputDebugString(wszBuff);
#endif
///////

    // If the SID passed in is the same as the user currently set, then there is no work to do
    if (IsValidSid(m_pUserSID) && EqualSid(m_pUserSID, const_cast<SID*>(pUser)))
    {
        return S_OK;
    }

    // free previous SID
    if (m_pUserSID)
    {
        LocalFree(m_pUserSID);
        m_pUserSID = NULL;
    }

    // Allocate memory for the user SID
    m_cbUserSID = GetLengthSid((PSID)pUser);
    m_pUserSID = (SID*)LocalAlloc(LMEM_FIXED, m_cbUserSID);

    if (m_pUserSID)
    {
        ZeroMemory(m_pUserSID, m_cbUserSID);
    }
    else
    {
        return E_OUTOFMEMORY;
    }

    // Set the current user to the SID passed in
    if (CopySid(m_cbUserSID, m_pUserSID, const_cast<SID*>(pUser)))
    {
        if (g_pDataManager)
        {
            // A new user has logged on. For this sample, we want to wipe the data off the device
            hr = g_pDataManager->DeleteAllApplications();
            if (SUCCEEDED(hr))
            {
                APPLICATION_ID* pAppIds = NULL;
                DWORD           cAppIds = 0;

                // Get the cached application order from the property store for this user
                hr = GetStoredApplicationOrder(&pAppIds, &cAppIds);
                if (SUCCEEDED(hr))
                {
                    // Set the application order in the data manager
                    HRESULT hrChangeSortOrder = g_pDataManager->ChangeSortOrder(pAppIds, cAppIds);
                    if (FAILED(hrChangeSortOrder))
                    {
                        hr = hrChangeSortOrder;
                    }
                }
                else
                {
                    // Not a failure. This means there isn't any stored order
                    hr = S_OK;
                }

                if (NULL != pAppIds)
                {
                    ::CoTaskMemFree(pAppIds);
                }
            }

#ifdef _ASSIGNED_USER_MODEL
            //
            // save assigned user SID to the Registry
            //
            LPWSTR wszSid = NULL;
            
            if (ConvertSidToStringSid(m_pUserSID, &wszSid))
            {
                PROPVARIANT pv = {0};

                PropVariantInit(&pv);

                pv.vt = VT_LPWSTR;
                pv.pwszVal = wszSid;

                m_pPropertyStore->SetNamedValue(REG_NAME_LAST_ASSIGNED_USER, &pv);

                LocalFree(wszSid);
            }
#endif
        }
        else
        {
            hr = E_POINTER;
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}


/////////////////////////////////////////////////////////////////////////
//
// OnGetCurrentUser()
//
// Retrieves the current user of the device.
//
// Parameters:
//      param1 [in]
//          A pointer to SID pointer that upon return will point to the SID of
//          the current user of the device.  Note that the caller will free the
//          SID by calling LocalFree().
//
//          If the device is a "console user model" device (device owned by the
//          person interactively logged on at the console), then this will
//          always return the WinInteractiveSid SID. If the device is an
//          "assigned user model" device (where the device chooses who the owner
//          is), then this should return the SID of the currently owned user.
//
// Return Values:
//      S_OK: On success.
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnGetCurrentUser(/*[out]*/ SID** ppUser)
{
    HRESULT hr = S_OK;

    if (NULL == ppUser)
    {
        return E_POINTER;
    }

#if 0
#if DBG
    WCHAR wszBuff[128];
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), L"OnGetCurrentUser\r\n");
    OutputDebugString(wszBuff);
#endif
#endif

#ifdef _ASSIGNED_USER_MODEL
    //
    // Assigned User Model:
    // if m_pUserSDI is set valid SID, return it otherwise return NULL SID.
    // m_pUserSID is set in OnSetCurrentUser() Or Initialize() from LastAssignedUser registry value.
    // 
    if (m_pUserSID == NULL || IsValidSid(m_pUserSID) == FALSE)
    {
        SID *pUser = NULL;
        DWORD cbUser = SECURITY_MAX_SID_SIZE;

        pUser = (SID*)LocalAlloc(LMEM_FIXED, cbUser);
        
        if (pUser)
        {
            if (CreateWellKnownSid(WinNullSid, NULL, pUser, &cbUser))
            {
                *ppUser = pUser;
            }
            else
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
                LocalFree(pUser);
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else
    {
        SID *pUser = NULL;
        pUser = (SID*)LocalAlloc(LMEM_FIXED, m_cbUserSID);
        
        if(pUser)
        {
            if (CopySid(m_cbUserSID, pUser, m_pUserSID))
            {
                *ppUser = pUser;
            }
            else
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
                LocalFree(pUser);
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
#else
    //
    // Console User Model:
    // return Interactive SID.
    //
    SID *pUser = NULL;
    DWORD cbUser = SECURITY_MAX_SID_SIZE;

    pUser = (SID*)LocalAlloc(LMEM_FIXED, cbUser);
    
    if (pUser)
    {
        if (CreateWellKnownSid(WinInteractiveSid, NULL, pUser, &cbUser))
        {
            *ppUser = pUser;
        }
        else
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            LocalFree(pUser);
        }
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
#endif
    
#if 0
#if DBG
    if (SUCCEEDED(hr))
    {
        WCHAR wszName[64];
        WCHAR wszDomain[64];
        DWORD cchName = COUNTOF(wszName);
        DWORD cchDomain = COUNTOF(wszDomain);
        SID_NAME_USE SidType;

        LookupAccountSid(NULL, (SID *)pUser, wszName, &cchName, wszDomain, &cchDomain, &SidType);
        StringCchPrintf(wszBuff, COUNTOF(wszBuff), L" -> %s\\%s\r\n", wszDomain, wszName);
        OutputDebugString(wszBuff);
    }
#endif
#endif

    return hr;
}


/////////////////////////////////////////////////////////////////////////
//
// OnSetUserState()
//
// Sets a users' availability status.  This method is called to indicate to a
// device if a user is available.  This information is useful for devices that
// allow users to assign a specific user as the owner of the device.
//
// Parameters:
//      user [in]
//          A const pointer to a SID that identifies the users whose state is
//          changing.
//      state [in]
//          The state of the changing user; possible values include:
//          AVAILABLE -     A user has become available to the platform, such as
//                          from a logon.
//          UNAVAILABLE -   A user has become unavailable to the platform, such
//                          as from a logoff.
//
// Return Values:
//      S_OK:
//
/////////////////////////////////////////////////////////////////////////

HRESULT CWssBasicDDI::OnSetUserState(const SID* pUserSID, const USER_STATE state)
{
    HRESULT hr = S_OK;

#ifdef _ASSIGNED_USER_MODEL

///////
#if DBG
    WCHAR wszBuff[128];
    WCHAR wszName[64];
    WCHAR wszDomain[64];
    DWORD cchName = COUNTOF(wszName);
    DWORD cchDomain = COUNTOF(wszDomain);
    SID_NAME_USE SidType;

    LookupAccountSid(NULL, (SID *)pUserSID, wszName, &cchName, wszDomain, &cchDomain, &SidType);

    StringCchPrintf(wszBuff, COUNTOF(wszBuff), L"OnSetUserState %s\\%s state=%d\r\n", wszDomain, wszName, state);
    OutputDebugString(wszBuff);
#endif
///////

    if (g_pDataManager)
    {
        if (state == AVAILABLE)
        {
            hr = g_pDataManager->AddUser(pUserSID);
        }
        else
        {
            hr = g_pDataManager->RemoveUser(pUserSID);
        }
    }

#else
    UNREFERENCED_PARAMETER(pUserSID);
    UNREFERENCED_PARAMETER(state);
#endif

    return hr;
}


HRESULT CWssBasicDDI::OnSetTime(/*[in]*/ const FILETIME time)
{
    (time);

    return S_OK;
}


HRESULT CWssBasicDDI::OnSetTimeZone(/*[in]*/ const SIDESHOW_TIME_ZONE_INFORMATION* pTimeZoneInformation)
{
    (pTimeZoneInformation);

    return S_OK;
}


HRESULT CWssBasicDDI::OnSetShortDateFormat(
        /*[in]*/            const SID*  user,
        /*[in, string]*/    LPCWSTR     wszDateFormat
        )
{
    (user);
    (wszDateFormat);

    return S_OK;
}


HRESULT CWssBasicDDI::OnSetLongDateFormat(
        /*[in]*/            const SID*  user,
        /*[in, string]*/    LPCWSTR     wszDateFormat
        )
{
    (user);
    (wszDateFormat);

    return S_OK;
}


HRESULT CWssBasicDDI::OnSetShortTimeFormat(
        /*[in]*/            const SID*  user,
        /*[in, string]*/    LPCWSTR     wszTimeFormat
        )
{
    (user);
    (wszTimeFormat);

    return S_OK;
}


HRESULT CWssBasicDDI::OnSetLongTimeFormat(
        /*[in]*/            const SID*  user,
        /*[in, string]*/    LPCWSTR     wszTimeFormat
        )
{
    (user);
    (wszTimeFormat);

    return S_OK;
}


HRESULT CWssBasicDDI::OnSetTheme(LPCWSTR wszTheme)
{
    (wszTheme);

    return S_OK;
}


HRESULT CWssBasicDDI::OnGetTheme(__deref_out LPWSTR* pwszTheme)
{
    (pwszTheme);

    return S_OK;
}


/////////////////////////////////////////////////////////////////////////
//
// OnGetDeviceFirmwareVersion()
//
// Retrieves the firmware version of the firmware running on the device.
//
// Parameters:
//      pwszVersion [in]
//          A pointer to a LPWSTR that upon return will point to a string that
//          indicates the firmware version running on the device.  This string
//          will be freed by the caller with a call to CoTaskMemFree().
// Return Values:
//      S_OK: On success.
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnGetDeviceFirmwareVersion(__deref_out LPWSTR* pwszVersion)
{
    if (NULL == pwszVersion)
    {
        return E_POINTER;
    }

    //
    // These values are hard coded here (string is set in DeviceSpecific.cpp).
    // Alternatively, they could be retrieved from the device.
    //
    size_t SizeOfDeviceFirmwareVersion = (wcslen(CDevice::GetDeviceFirmwareVersion()) + 1) * sizeof(wchar_t);
    *pwszVersion = (LPWSTR)CoTaskMemAlloc(SizeOfDeviceFirmwareVersion);
    memcpy(*pwszVersion, CDevice::GetDeviceFirmwareVersion(), SizeOfDeviceFirmwareVersion);

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////
//
// CWssBasicDDI::OnGetDeviceManufacturer
//
// Retrieves the device manufacturer string from the device
//
// Parameters:
//      pwszManufacturer - pointer to a LPWSTR that upon return will
//      point to a string that indicates the manufacturer of the device.
//      The string will be freed by the caller with a call to CoTaskMemFree()
//
// Return Values:
//      S_OK: successfully returned a string
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnGetDeviceManufacturer(__deref_out LPWSTR* pwszManufacturer)
{
    if (NULL == pwszManufacturer)
    {
        return E_POINTER;
    }

    HRESULT hr = S_OK;

    //
    // These values are hard coded here (string is set in DeviceSpecific.cpp).
    // Alternatively, they could be retrieved from the device.
    //
    size_t SizeOfDeviceManufacturer = (wcslen(CDevice::GetDeviceManufacturer()) + 1) * sizeof(wchar_t);
    *pwszManufacturer = (LPWSTR)CoTaskMemAlloc(SizeOfDeviceManufacturer);
    memcpy(*pwszManufacturer, CDevice::GetDeviceManufacturer(), SizeOfDeviceManufacturer);

    return hr;
}

/////////////////////////////////////////////////////////////////////////
//
// CWssBasicDDI::OnGetDeviceName
//
// Retrieves the device name string from the device.
//
// Parameters:
//      pwszName - pointer to a LPWSTR that upon return will point to
//      a string that indicates the name of the device.  The string will
//      be freed by the caller with a call to CoTaskMemFree().
//
// Return Values:
//      S_OK: successfully returned a string
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnGetDeviceName(__deref_out LPWSTR* pwszName)
{
    if (NULL == pwszName)
    {
        return E_POINTER;
    }

    HRESULT hr = S_OK;

    //
    // These values are hard coded here (string is set in DeviceSpecific.cpp).
    // Alternatively, they could be retrieved from the device.
    //
    size_t SizeOfDeviceFriendlyName = (wcslen(CDevice::GetDeviceFriendlyName()) + 1) * sizeof(wchar_t);
    *pwszName = (LPWSTR)CoTaskMemAlloc(SizeOfDeviceFriendlyName);
    memcpy(*pwszName, CDevice::GetDeviceFriendlyName(), SizeOfDeviceFriendlyName);

    return hr;
}

/////////////////////////////////////////////////////////////////////////
//
// OnGetDeviceEndpoints()
//
// Retrieves the Endpoints that are available on the device.
//
// Parameters:
//      rgEndpoints [out]
//          A pointer to an array of ENDPOINT_IDs that upon return will contain
//          the available endpoints on the device.  Ownership of the array is
//          passed to the caller, who should free the array by calling
//          CoTaskMemFree().
//      pcEndpoints [out]
//          A pointer to a DWORD that upon return will contain the count of
//          endpoint IDs in the array of endpoint, rgEndpoints.
//
// Return Values:
//      S_OK: Success
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnGetDeviceEndpoints(ENDPOINT_ID** rgEndpoints,
                                           DWORD* pcEndpoints)
{
    if ((NULL == rgEndpoints) || (NULL == pcEndpoints))
    {
        return E_POINTER;
    }

    *rgEndpoints = (ENDPOINT_ID*)CoTaskMemAlloc(sizeof(ENDPOINT_ID));
    memcpy(*rgEndpoints, &SIDESHOW_ENDPOINT_SIMPLE_CONTENT_FORMAT, sizeof(SIDESHOW_ENDPOINT_SIMPLE_CONTENT_FORMAT));
    *pcEndpoints = 1;

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////
//
// OnGetDeviceCapabilities()
//
// This method, given a capability descriptor, will return a value for that
// capability indicating the existence and extent of the capability.
//
// Parameters:
//      pKey : [in]
//        A pointer to a PROPERTYKEY that describes the capability to query.
//      pvValue : [out]
//        A pointer to a PROPVARIANT that upon return will contain the value for
//        the capability specified by the pguidCatagory + pid.
//
// Return Values:
//        S_OK : Success
//        S_FALSE : The specified capability is not supported by the device.
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnGetDeviceCapabilities(
        /*[in]*/    const PROPERTYKEY*  pKey,
        /*[out]*/   PROPVARIANT*        pvValue
        )
{
    HRESULT hr = S_OK;
    
    if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_SCREEN_TYPE))
    {
        pvValue->vt = VT_I4;
        pvValue->lVal = SIDESHOW_SCREEN_TYPE_BITMAP;
    }
    else if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_SCREEN_WIDTH))
    {
        pvValue->vt = VT_UI2;
        pvValue->uiVal = (USHORT)CDevice::GetDeviceHorizontalResolution();
    }
    else if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_SCREEN_HEIGHT))
    {
        pvValue->vt = VT_UI2;
        pvValue->uiVal = (USHORT)CDevice::GetDeviceVerticalResolution();
    }
    else if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_COLOR_DEPTH))
    {
        pvValue->vt = VT_UI2;
        pvValue->uiVal = (USHORT)CDevice::GetDeviceBitDepth();
    }
    else if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_COLOR_TYPE))
    {
        pvValue->vt = VT_I4;
        if (CDevice::GetDeviceBitDepth() == 1)
        {
            pvValue->lVal = SIDESHOW_COLOR_TYPE_BLACK_AND_WHITE;
        }
        else
        {
            pvValue->lVal = SIDESHOW_COLOR_TYPE_COLOR;
        }
    }
    else if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_CLIENT_AREA_WIDTH))
    {
        pvValue->vt = VT_UI2;
        pvValue->uiVal = (USHORT)CDevice::GetDeviceHorizontalResolution() - CONTENT_LEFT_MARGIN - CONTENT_RIGHT_MARGIN;
    }
    else if  (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_CLIENT_AREA_HEIGHT))
    {
        pvValue->vt = VT_UI2;
        pvValue->uiVal = (USHORT)(CDevice::GetDeviceVerticalResolution() - CRenderer::GetTitlebarHeight());
    }
    else if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_SUPPORTED_IMAGE_FORMATS))    // VT_LPWSTR
    {
        pvValue->vt = VT_LPWSTR;
        pvValue->pwszVal = AtlAllocTaskWideString(L"image/gif;image/bmp;image/jpeg");
    }
    else if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_SUPPORTED_LANGUAGES) ||
            IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_CURRENT_LANGUAGE))
    {
        WCHAR wszLocaleName[16];
        GetSystemDefaultLocaleName(wszLocaleName, COUNTOF(wszLocaleName));
        pvValue->vt = VT_LPWSTR;
        pvValue->pwszVal = AtlAllocTaskWideString(wszLocaleName);
    }
    else if (IsEqualPropertyKey(*pKey, SIDESHOW_CAPABILITY_SUPPORTED_THEMES))
    {
        pvValue->vt = VT_LPWSTR;
        pvValue->pwszVal = AtlAllocTaskWideString(L"default");
    }
    else
    {
        hr = E_NOTIMPL;
    }

    return hr;
}

/////////////////////////////////////////////////////////////////////////
//
// OnGetApplicationOrder()
//
// Retrieves the order that applications should be listed in by the CPL.
//
// Parameters:
//      ppAppIds [out]
//          A pointer to an array of APPLICATION_IDs that upon return will
//          contain the application IDs in the order that they should be
//          presented in the WSS CPL.
//      pcAppIds [out]
//          A pointer to a DWORD that upon return will contain the count of
//          APPLICATION_ID contained in the array pointed to by ppAppIds.
//
// Return Values:
//      S_OK: Success
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnGetApplicationOrder(
        APPLICATION_ID**   ppAppIds,
        DWORD*             pcAppIds
        )
{
    if (NULL == m_pUserSID ||
        0 == IsValidSid(m_pUserSID) ||
        m_pPropertyStore == NULL ||
        NULL == g_pDataManager)
    {
        return E_UNEXPECTED;
    }

    //
    // Get the gadget order from the DataManager
    //
    return g_pDataManager->GetApplications(ppAppIds, pcAppIds);
}

/////////////////////////////////////////////////////////////////////////
//
// GetStoredApplicationOrder()
//
// Retrieves the gadget order from the property store.  The caller must make the memory CoTaskMemFree.
//
// Parameters:
//      ppAppIds [out]
//          A pointer to an array of APPLICATION_IDs that upon return will
//          contain the application IDs in the order that they should be
//          presented on the device
//      pcAppIds [out]
//          A pointer to a DWORD that upon return will contain the count of
//          APPLICATION_ID contained in the array pointed to by ppAppIds.
//
// Return Values:
//      S_OK: Success
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::GetStoredApplicationOrder(
        APPLICATION_ID**   ppAppIds,
        DWORD*             pcAppIds
        )
{
    if (NULL == m_pUserSID ||
        0 == IsValidSid(m_pUserSID) ||
        m_pPropertyStore == NULL)
    {
        return E_UNEXPECTED;
    }

    HRESULT hr = S_OK;
    LPTSTR  wszKey = NULL;

    // Initialize out parameters
    *ppAppIds = NULL;
    *pcAppIds = 0;

    //
    // Get the key to use for the property store
    //
    hr = GetApplicationOrderString(&wszKey);

    //
    // Put the data into a PROPVARIANT, and store it in the property store
    //
    if (SUCCEEDED(hr))
    {
        PROPVARIANT pvBlob = {0};

        PropVariantInit(&pvBlob);

        hr = m_pPropertyStore->GetNamedValue(wszKey, &pvBlob);
        if (SUCCEEDED(hr) &&
            VT_BLOB == pvBlob.vt &&
            0 == (pvBlob.blob.cbSize % sizeof(APPLICATION_ID)))
        {
            *pcAppIds = pvBlob.blob.cbSize / sizeof(APPLICATION_ID);
            *ppAppIds = (APPLICATION_ID*)pvBlob.blob.pBlobData;
            
            // Don't clear the PROPVARIANT because we don't want to erase the memory that we pass out of this method
        }
        else
        {
            PropVariantClear(&pvBlob);
        }
    }

    if (NULL != wszKey)
    {
        ::CoTaskMemFree(wszKey);
    }

    return hr;
}

/////////////////////////////////////////////////////////////////////////
//
// SetStoredApplicationOrder()
//
// Stores the application order in the property store for the device
//
// Parameters:
//      pApps [in]
//          An array of APPLICATION_IDs.
//      cApps [out]
//          A DWORD that is the count of application IDs in the array, pApps.
//
// Return Values:
//      S_OK: Success
//      E_INVALIDARG: cApps was == 0 or > 1024
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::SetStoredApplicationOrder(
        const APPLICATION_ID*   pApps,
        const DWORD             cApps
        )
{
    if (NULL == m_pUserSID ||
        0 == IsValidSid(m_pUserSID) ||
        m_pPropertyStore == NULL)
    {
        return E_UNEXPECTED;
    }

    HRESULT hr = S_OK;
    LPTSTR  wszKey = NULL;

    //
    // Get the key to use for the property store
    //
    hr = GetApplicationOrderString(&wszKey);

    //
    // Put the data into a PROPVARIANT, and store it in the property store
    //
    if (SUCCEEDED(hr))
    {
        PROPVARIANT pvBlob = {0};

        PropVariantInit(&pvBlob);

        pvBlob.vt = VT_BLOB;
        pvBlob.blob.cbSize = cApps * sizeof(APPLICATION_ID);
        pvBlob.blob.pBlobData = (BYTE*)pApps;

        hr = m_pPropertyStore->SetNamedValue(wszKey, &pvBlob);

        // Don't clear the PROPVARIANT because we don't want to erase the memory that we don't own.
    }

    if (NULL != wszKey)
    {
        ::CoTaskMemFree(wszKey);
    }

    return hr;
}


/////////////////////////////////////////////////////////////////////////
//
// OnSetApplicationOrder()
//
// Sends an array of application IDs to the device that is the order in which
// they are presented in the WSS CPL.
//
// Parameters:
//      pApps [in]
//          An array of APPLICATION_IDs.
//      cApps [out]
//          A DWORD that is the count of application IDs in the array, pApps.
//
// Return Values:
//      S_OK: Success
//      E_INVALIDARG: cApps was == 0 or > 1024
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnSetApplicationOrder(
        const APPLICATION_ID*   pApps,
        const DWORD             cApps
        )
{
    if (NULL == g_pDataManager)
    {
        return E_UNEXPECTED;
    }

    HRESULT hr = S_OK;

    // Store the order in the property store
    hr = SetStoredApplicationOrder(pApps, cApps);
    if (SUCCEEDED(hr))
    {
        // Ensure the data manager updates to reflect the new order
        hr = g_pDataManager->ChangeSortOrder(pApps, cApps);
    }

    return hr;
}


/////////////////////////////////////////////////////////////////////////
//
// OnGetPreEnabledApplications()
//
// Retrieves the list of gadgets that should be pre-enabled for all users.
//
// Parameters:
//      ppAppIds [out]
//          A pointer to an array of APPLICATION_IDs that upon return will
//          contain the application IDs that should be pre-enabled for this
//          device.  The corresponding endpoint is at the same index in
//          the endpoint array
//      ppEndpointIds [out]
//          A pointer to an array of ENDPOINT_IDs that upon return will
//          contain the endpoint IDs that should be pre-enabled for this
//          device.  The corresponding application is at the same index in
//          the application array
//      pcAppIds [out]
//          A pointer to a DWORD that upon return will contain the count of
//          APPLICATION_ID/ENDPOINT_ID pairs contained in the arrays pointed
//          to by ppAppIds and ppEndpointIds.
//
// Return Values:
//      S_OK: Success
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnGetPreEnabledApplications(APPLICATION_ID** ppAppIds,
                                                  ENDPOINT_ID** ppEndpointIds,
                                                  DWORD* pcApplications)
{
    if ((NULL == ppAppIds) ||
        (NULL == ppEndpointIds) ||
        (NULL == pcApplications))
    {
        return E_POINTER;
    }

    // Sample code for pre-enabled applications (gadgets)
    // The following commented out code uses hard-coded AppIds. Alternatively, you could poll
    // a device for the AppIds or set properties in the inf file which could be read here.
    *pcApplications = 0;

    //const DWORD dwNumberOfPreEnabledGadgets = 1;

    //*ppAppIds = (APPLICATION_ID*)CoTaskMemAlloc(sizeof(APPLICATION_ID) * dwNumberOfPreEnabledGadgets);
    //if (NULL == *ppAppIds)
    //{
        //return E_OUTOFMEMORY;
    //}

    //*ppEndpointIds = (ENDPOINT_ID*)CoTaskMemAlloc(sizeof(ENDPOINT_ID) * dwNumberOfPreEnabledGadgets);
    //if (NULL == *ppEndpointIds)
    //{
        //CoTaskMemFree(*ppAppIds);
        //*ppAppIds = NULL;
        //return E_OUTOFMEMORY;
    //}

    //GUID fakeAppIdGuid = {0x11111111, 0x2222, 0x3333, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB};
    //(*ppAppIds)[0] = fakeAppIdGuid;
    //(*ppEndpointIds)[0] = SIDESHOW_ENDPOINT_SIMPLE_CONTENT_FORMAT;
    //*pcApplications = dwNumberOfPreEnabledGadgets;

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////
//
// OnAddApplication()
//
// This method constructs a packet, the purpose of which is to register an application
// with the device, and send that packet to the device.  Once this method has
// been called, it may be followed by calls to OnAddContent(), where the appID
// is the AppID specified in DoAddApp().
//
// Parameters:
//      pAppId : [in]
//        A const pointer to a GUID that is the application ID that will be
//        registered with the device and used for subsequent calls to
//        OnAddContent().
//      pEndPoint : [in]
//        A const pointer to a GUID that is the renderer/endpoint ID that the
//        registered app is to be associated with.
//      wszName : [in]
//        A LPCWSTR that is the name of the application that the device may use
//        for display purposes.
//      policy : [in]
//        A DWORD that indicates the cache policy to be used by the device for
//        the application policy will be a value that may have the following bit flags
//        OR'ed together:
//              KeepOldest
//              KeepFrequentlyAccessed
//              KeepRecentlyAccessed
//
//
// Return Values:
//        S_OK : Success
//        E_POINTER : wszName was NULL
//        E_OUTOFMEMORY : Couldn't allocate the necessary memory
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnAddApplication(
        /*[in]*/    const SID*              user,
        /*[in]*/    REFAPPLICATION_ID       rAppId,
        /*[in]*/    REFENDPOINT_ID          rEndPoint,
        /*[in, string]*/  LPCWSTR           wszName,
        /*[in]*/    const DWORD             dwCachePolicy,
        /* [in] */  const DWORD             dwOnlineOnly,
        /*[in, size_is(cbLargeIcon)]*/ const unsigned char* pbLargeIcon,
        /*[in]*/    const DWORD             cbLargeIcon,
        /*[in, size_is(cbSmallIcon)]*/ const unsigned char* pbSmallIcon,
        /*[in]*/    const DWORD             cbSmallIcon,
        /*[in, size_is(cbSmallIcon)]*/ const unsigned char* pbMiniIcon,
        /*[in]*/    const DWORD             cbMiniIcon
        )
{
    (user);
    (rEndPoint);
    (pbLargeIcon);
    (cbLargeIcon);
    (dwCachePolicy);
    (dwOnlineOnly);

    HRESULT hr = S_OK;

    if (NULL == wszName)
    {
        return E_POINTER;
    }

///////
#if DBG
    WCHAR wszBuff[128];
    WCHAR wszBuff2[64];
    StringFromGUID2(rAppId, wszBuff2, COUNTOF(wszBuff2));
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), L"OnAddApplication %s %s\r\n", wszBuff2, wszName);
    OutputDebugString(wszBuff);
#endif
///////

    if (NULL != g_pDataManager)
    {
        hr = g_pDataManager->AddApplication(rAppId,
                                                wszName,
                                                pbSmallIcon,
                                                cbSmallIcon,
                                                pbMiniIcon,
                                                cbMiniIcon);
    }
    else
    {
        hr = E_POINTER;
    }

    return hr;
}

HRESULT CWssBasicDDI::OnRemoveApplication(
        /*[in]*/    const SID*              user,
        /*[in]*/    REFAPPLICATION_ID       rAppId
        )
{
    (user);

///////
#if DBG
    WCHAR wszBuff[128];
    WCHAR wszBuff2[128];
    StringFromGUID2(rAppId, wszBuff2, COUNTOF(wszBuff2));
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), L"OnRemoveApplication %s\r\n", wszBuff2);
    OutputDebugString(wszBuff);
#endif
///////

    HRESULT hr = E_FAIL;
    if (NULL != g_pDataManager)
    {
        hr = g_pDataManager->DeleteApplication(rAppId);
    }
    else
    {
        hr = E_POINTER;
    }

    return hr;
}

HRESULT CWssBasicDDI::OnRemoveAllApplications(
        /* [in] */    const SID* user
        )
{
    (user);

    HRESULT hr = E_FAIL;
    if (NULL != g_pDataManager)
    {
        hr = g_pDataManager->DeleteAllApplications();
    }
    else
    {
        hr = E_POINTER;
    }

    return hr;
}


HRESULT CWssBasicDDI::OnSetNotificationsEnabled(const SID* user,
                                                const BOOL fIsEnabled)
{
    (user);
    (fIsEnabled);

    return S_OK;
}


// Return Values:
//        S_OK : Success
//        E_POINTER : wszTitle or wszMessage was NULL
//        E_OUTOFMEMORY : Couldn't allocate the necessary memory

HRESULT CWssBasicDDI::OnAddNotification(
        /*[in]*/        const SID*              user,
        /*[in]*/        REFAPPLICATION_ID       rAppId,
        /*[in]*/        const NOTIFICATION_ID   notificationId,
        /*[in]*/        const FILETIME          ftExpirationArg,
        /*[in, string]*/LPCWSTR                 wszTitle,
        /*[in, string]*/LPCWSTR                 wszMessage,
        /*[in, size_is(cbImage)]*/const unsigned char*   pbImage,
        /*[in]*/        const DWORD             cbImage
        )
{
    (user);

    HRESULT hr = S_OK;

    if ((NULL == wszTitle) || (NULL == wszMessage))
    {
        return E_POINTER;
    }

///////
#if DBG
    WCHAR wszBuff[128];
    SYSTEMTIME st;
    FileTimeToSystemTime(&ftExpirationArg, &st);
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), L"OnAddNotification %s %02d-%02d-%02d %02d:%02d:%02d.%03d\r\n", wszTitle,
        st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    OutputDebugString(wszBuff);
#endif
///////

    if (NULL != g_pDataManager)
    {
          //
          //  set automatic (programatic) dismissals of notification boxes to a minimum of 5 seconds.
          //
          CFileTime ftExpiration(ftExpirationArg);
          CFileTime ftMinExpire = CFileTime::GetCurrentTime().UTCToLocal() +
          CFileTimeSpan((LONGLONG)NOTIFICATION_MIN_SEC * 10000000);

     if (ftExpiration < ftMinExpire)
     {
            ftExpiration = ftMinExpire;
///////
#if DBG
            SYSTEMTIME st;
            FileTimeToSystemTime(&ftExpiration, &st);
            StringCchPrintf(wszBuff, COUNTOF(wszBuff), L"Adjusted ExpirationTime %d-%02d-%02d %02d:%02d:%02d.%03d\r\n",
                st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
            OutputDebugString(wszBuff);
#endif
///////
        }

        hr = g_pDataManager->AddNotification(rAppId,
                                            notificationId,
                                            wszTitle,
                                            wszMessage,
                                            pbImage,
                                            cbImage,
                                            ftExpiration);
    }
    else
    {
        hr = E_POINTER;
    }

    return hr;
}

HRESULT CWssBasicDDI::OnRemoveNotification(
        /*[in]*/    const SID*              user,
        /*[in]*/    REFAPPLICATION_ID       rAppId,
        /*[in]*/    const NOTIFICATION_ID   notificationId
        )
{
    (user);
    HRESULT hr = E_FAIL;
    if (NULL != g_pDataManager)
    {
        hr = g_pDataManager->DeleteNotification(rAppId,
                                                notificationId);
    }
    else
    {
        hr = E_POINTER;
    }

    return hr;
}

HRESULT CWssBasicDDI::OnRemoveAllNotifications(
        /*[in]*/    const SID*              user,
        /*[in]*/    REFAPPLICATION_ID       rAppId
        )
{
    (user);
    (rAppId);

    // Should this be implemented?

    return S_OK;
}


HRESULT CWssBasicDDI::OnAddContent(
        /*[in]*/    const SID*              user,
        /*[in]*/    REFAPPLICATION_ID       rAppId,
        /*[in]*/    REFENDPOINT_ID          rRendererId,
        /*[in]*/    const CONTENT_ID        contentId,
        /*[in, size_is(cbData)]*/const unsigned char* pData, // This is UTF8 data
        /*[in]*/    const DWORD             cbData
        )
{
    (user);
    (rRendererId);

    HRESULT hr = S_OK;

    if (0 == contentId) // glance text
    {
        if (cbData == 0 || (cbData == 1 && *pData == '\0'))
        {
            hr = g_pDataManager->UpdateApplicationNode(rAppId, L"");
        }
        else
        {
            LPWSTR pwszData = NULL;
            DWORD cchData = 0;
            
            hr = UTF8ToWideChar(pData, cbData, &pwszData, &cchData);
            
            if (SUCCEEDED(hr))
            {
                hr = g_pDataManager->UpdateApplicationNode(rAppId, pwszData);
                
                delete [] pwszData;
            }
        }
    }
    else
    {
        hr = g_pDataManager->AddContent(rAppId, contentId, pData, cbData);
    }
    
    return hr;
}

HRESULT CWssBasicDDI::OnRemoveContent(
        /*[in]*/    const SID*              user,
        /*[in]*/    REFAPPLICATION_ID       rAppId,
        /*[in]*/    REFENDPOINT_ID          rRendererId,
        /*[in]*/    const CONTENT_ID        contentId
        )
{
    UNREFERENCED_PARAMETER(user);
    UNREFERENCED_PARAMETER(rRendererId);

    HRESULT hr = S_OK;
    
    if (0 != contentId)
    {
        hr = g_pDataManager->RemoveContent(rAppId, contentId);
    }
    
    return hr;
}

HRESULT CWssBasicDDI::OnRemoveAllContent(
        /*[in]*/    const SID*              user,
        /*[in]*/    REFAPPLICATION_ID       rAppId,
        /*[in]*/    REFENDPOINT_ID          rRendererId
        )
{
    (user);
    (rRendererId);

    return g_pDataManager->RemoveAllContent(rAppId);
}


// This method is one of two ways to support extensibility in the driver
// This one allows custom WPD commands to be sent to the driver and processed
HRESULT CWssBasicDDI::OnProcessWpdMessage(IUnknown* pinValues,
                                          IUnknown* poutValues)
{
    (poutValues);

    IPortableDeviceValues* pPortableDeviceValuesParams = NULL;
    // Also define pPortableDeviceValuesResults if you intend to send results information
    //IPortableDeviceValues* pPortableDeviceValuesResults = NULL;

    HRESULT hr = pinValues->QueryInterface(IID_IPortableDeviceValues, (VOID**)&pPortableDeviceValuesParams);
    if (FAILED(hr))
    {
        return hr;
    }
    if (NULL == pPortableDeviceValuesParams)
    {
        return E_POINTER;
    }

    // Check if the incoming message is a custom command we intend to act on
    PROPERTYKEY propertyKey = {0};
    hr = pPortableDeviceValuesParams->GetGuidValue(WPD_PROPERTY_COMMON_COMMAND_CATEGORY, &propertyKey.fmtid);
    if (S_OK == hr)
    {
        hr = pPortableDeviceValuesParams->GetUnsignedIntegerValue(WPD_PROPERTY_COMMON_COMMAND_ID, &propertyKey.pid);
    }
    if (S_OK == hr)
    {
        // The next lines check if the custom command is something this driver acts on if necessary
        if (IsEqualPropertyKey(CUSTOM_COMMAND_CHANGE_SOMETHING, propertyKey))
        {
            DWORD dwSomething = 0;
            hr = pPortableDeviceValuesParams->GetUnsignedIntegerValue(CUSTOM_COMMAND_CHANGE_SOMETHING_SETVALUE, &dwSomething);
            if (S_OK == hr)
            {
                // Change Something Here
                // If needed, render the page again: g_Device.HandleDeviceEvent(CDevice::RenderAgain);
            }
        }
    }

    pPortableDeviceValuesParams->Release();
    pPortableDeviceValuesParams = NULL;

    return hr;
}

/////////////////////////////////////////////////////////////////////////
//
// OnSetLanguage()
//
// Sets the current language on the device.
//
// Parameters:
//      wszLang [in]
//          A string that indicates the current language.
//
// Return Values:
//      S_OK: Success
//
/////////////////////////////////////////////////////////////////////////
HRESULT CWssBasicDDI::OnSetLanguage(
        const SID*  user,
        LPCWSTR wszLang
        )
{
    (user);
    (wszLang);

    return S_OK;
}

//
// SideShow Events
//

HRESULT CWssBasicDDI::SendContentMissingEvent(REFAPPLICATION_ID rAppId, CONTENT_ID contentId)
{
    HRESULT hr = S_OK;

#if DBG
    WCHAR wszBuff[256];
    StringCchPrintf(wszBuff, COUNTOF(wszBuff), L"Sending Content Missing Event ContentId =  %d\r\n", contentId);
    OutputDebugString(wszBuff);
#endif

    CONTENT_MISSING_EVENT_DATA event;

    event.cbContentMissingEventData = sizeof(CONTENT_MISSING_EVENT_DATA);
    event.ApplicationId = rAppId;
    event.EndpointId = SIDESHOW_ENDPOINT_SIMPLE_CONTENT_FORMAT;
    event.ContentId = contentId;

    hr = m_pWdfDevice->PostEvent(SIDESHOW_CONTENT_MISSING_EVENT,
                                 WdfEventBroadcast,
                                 (BYTE*)&event,
                                 event.cbContentMissingEventData);

    return hr;
}

HRESULT CWssBasicDDI::SendApplicationEvent(REFAPPLICATION_ID rAppId, DWORD dwEventId, BYTE *pEventData, DWORD cbEventData)
{
    HRESULT hr = S_OK;
    DWORD dwSize;

    BYTE *pbEvent = NULL;

    dwSize = offsetof(APPLICATION_EVENT_DATA, bEventData) + cbEventData;

    pbEvent = new(std::nothrow) BYTE[dwSize];

    if (pbEvent)
    {
        APPLICATION_EVENT_DATA* pEvent = (APPLICATION_EVENT_DATA*)pbEvent;

        pEvent->cbApplicationEventData = dwSize;
        pEvent->ApplicationId = rAppId;
        pEvent->EndpointId = SIDESHOW_ENDPOINT_SIMPLE_CONTENT_FORMAT;
        pEvent->dwEventId = dwEventId;
        pEvent->cbEventData = cbEventData;

        if (cbEventData)
        {
            memcpy(pEvent->bEventData, pEventData, cbEventData);
        }

        hr = m_pWdfDevice->PostEvent(SIDESHOW_APPLICATION_EVENT,
                                WdfEventBroadcast,
                                pbEvent,
                                dwSize);

        delete [] pbEvent;
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}


HRESULT CWssBasicDDI::SendNavigationEvent(REFAPPLICATION_ID rAppId, CONTENT_ID PreviousPage, CONTENT_ID TargetPage, UINT32 Button)
{
    HRESULT hr = S_OK;

    SCF_NAVIGATION_EVENT event;

    event.PreviousPage = PreviousPage;
    event.TargetPage = TargetPage;
    event.Button = Button;

    hr = SendApplicationEvent(rAppId,
                                 SCF_EVENT_NAVIGATION,
                                 (BYTE*)&event,
                                 sizeof(event));

    return hr;
}

HRESULT CWssBasicDDI::SendMenuActionEvent(
    REFAPPLICATION_ID rAppId,
    CONTENT_ID PreviousPage,
    CONTENT_ID TargetPage,
    UINT32 Button, UINT32 ItemId)
{
    HRESULT hr = S_OK;

    SCF_MENUACTION_EVENT event;

    event.PreviousPage = PreviousPage;
    event.TargetPage = TargetPage;
    event.Button = Button;
    event.ItemId = ItemId;

    hr = SendApplicationEvent(rAppId,
                        SCF_EVENT_MENUACTION,
                        (BYTE*)&event,
                        sizeof(event));

    return hr;
}

HRESULT CWssBasicDDI::SendContextMenuEvent(
    REFAPPLICATION_ID rAppId,
    CONTENT_ID PreviousPage,
    CONTENT_ID TargetPage,
    UINT32 PreviousItemId,
    CONTENT_ID MenuPage,
    UINT32 MenuItemId)
{
    HRESULT hr = S_OK;

    SCF_CONTEXTMENU_EVENT event;

    event.PreviousPage = PreviousPage;
    event.TargetPage = TargetPage;
    event.PreviousItemId = PreviousItemId;
    event.MenuPage = MenuPage;
    event.MenuItemId = MenuItemId;

    hr = SendApplicationEvent(rAppId,
                        SCF_EVENT_CONTEXTMENU,
                        (BYTE*)&event,
                        sizeof(event));

    return hr;
}

#ifdef _ASSIGNED_USER_MODEL
HRESULT CWssBasicDDI::SendUserChangeRequestEvent(const SID *pSID)
{
    HRESULT hr = S_OK;
    LPWSTR  pwszSid  = NULL;
    size_t cchSid = 0;

    if (!ConvertSidToStringSid((SID *)pSID, &pwszSid))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    if (SUCCEEDED(hr))
    {
        hr = StringCchLength(pwszSid, STRSAFE_MAX_CCH, &cchSid);
    }

    if (SUCCEEDED(hr))
    {
        DWORD dwSize = sizeof(DEVICE_USER_CHANGE_EVENT_DATA) + ((DWORD)cchSid * sizeof(WCHAR));
        BYTE *pbEvent = new(std::nothrow) BYTE[dwSize];

        if (pbEvent)
        {
            DEVICE_USER_CHANGE_EVENT_DATA* pEvent = (DEVICE_USER_CHANGE_EVENT_DATA*)pbEvent;

            pEvent->cbDeviceUserChangeEventData  = dwSize;

            memcpy(&pEvent->wszUser, pwszSid, cchSid * sizeof(WCHAR));
            (&pEvent->wszUser)[cchSid] = L'\0';

            hr = m_pWdfDevice->PostEvent(SIDESHOW_USER_CHANGE_REQUEST_EVENT,
                                        WdfEventBroadcast,
                                        pbEvent,
                                        dwSize);

            delete [] pbEvent;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if (pwszSid)
    {
        LocalFree(pwszSid);
    }

    return hr;
}
#endif

